/*
  This file is a part of hashutils package.
  (c) 2004 Zeljko Vrba <zvrba@globalnet.hr>

  Permission is hereby granted, free of charge, to any person obtaining
  a copy of this software and associated documentation files (the
  "Software"), to deal in the Software without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sfio.h>
#include <cdt.h>
#include <mycrypt.h>
#include "main.h"
#include "cdt_line.h"

static char rcsid[] = "$Id: hcomm.c 2 2007-03-29 14:43:55Z zeljko.vrba $";

const char *Main_argv0;
static unsigned int columns = 7;	/* output all 3 columns */

static void comm(Sfio_t *file1, Dt_t *set1, Sfio_t *file2, Dt_t *set2,
				 Sfio_t *out, int halg)
{
	char *record;
	Void_t *obj;
	char hash[256];

	if(Cdt_LineDiscipline.size > 256) {
		sfprintf(sfstderr, "FATAL: hash has more than 256 bytes (2048 bits), exiting.\n");
		exit(1);
	}

	while((record = sfgetr(file1, '\n', 0))) {
		unsigned long outlen = Cdt_LineDiscipline.size;
		unsigned int linelen = sfvalue(file1);
		int err;

		if((err = hash_memory(halg, (unsigned char*)record,
							  linelen, hash, &outlen)) != CRYPT_OK) {
			sfprintf(sfstderr, "FATAL: crypto error: %s, exiting.\n",
					 error_to_string(err));
			exit(1);
		}
		if((obj = dtmatch(set2, hash))) { /* exists in both files */
			if(columns & 4) {
				record[linelen-1] = 0;
				sfprintf(out, "\t\t%s\n", record);
			}
		} else if(columns & 1) { /* only in first file */
				record[linelen-1] = 0;
				sfprintf(out, "%s\n", record);
		}
	}
	if(sferror(file1)) {
		sfprintf(sfstderr, "ERROR reading FILE1: sfgetr: %s\n", strerror(errno));
		exit(1);
	}

	while((record = sfgetr(file2, '\n', 0))) {
		unsigned long outlen = Cdt_LineDiscipline.size;
		unsigned int linelen = sfvalue(file2);
		int err;

		if((err = hash_memory(halg, (unsigned char*)record,
							  linelen, hash, &outlen)) != CRYPT_OK) {
			sfprintf(sfstderr, "FATAL: crypto error: %s, exiting.\n",
					 error_to_string(err));
			exit(1);
		}
		if(!dtmatch(set1, hash) && (columns & 2)) { /* only in 2nd file */
			record[linelen-1] = 0;
			sfprintf(out, "\t%s\n", record);
		}
	}
	if(sferror(file2)) {
		sfprintf(sfstderr, "ERROR reading FILE2: sfgetr: %s\n", strerror(errno));
		exit(1);
	}
}

/*
  Optimized version with output generated as file2 is read. This is possible
  because we don't need to output lines unique to file1.
*/
static void comm1(Dt_t *set1, Sfio_t *file2, Sfio_t *out, int halg)
{
	char *record;
	Void_t *obj;
	char hash[256];

	if(Cdt_LineDiscipline.size > 256) {
		sfprintf(sfstderr, "FATAL: hash has more than 256 bytes (2048 bits), exiting.\n");
		exit(1);
	}

	while((record = sfgetr(file2, '\n', 0))) {
		unsigned long outlen = Cdt_LineDiscipline.size;
		unsigned int linelen = sfvalue(file2);
		int err;

		if((err = hash_memory(halg, (unsigned char*)record,
							  linelen, hash, &outlen)) != CRYPT_OK) {
			sfprintf(sfstderr, "FATAL: crypto error: %s, exiting.\n",
					 error_to_string(err));
			exit(1);
		}
		if((obj = dtmatch(set1, hash))) { /* exists in both files */
			if(columns & 4) {
				record[linelen-1] = 0;
				sfprintf(out, "\t\t%s\n", record);
			}
		} else if(columns & 2) { /* only in second file */
				record[linelen-1] = 0;
				sfprintf(out, "%s\n", record);
		}
	}
	if(sferror(file2)) {
		sfprintf(sfstderr, "ERROR reading FILE2: sfgetr: %s\n", strerror(errno));
		exit(1);
	}
}



/******************************************************************************
 * Implementation of 'main' interface.
 *****************************************************************************/
void Main_usage(const char *message)
{
	sfprintf(sfstderr, "%s\n", message);
	sfprintf(sfstderr, "USAGE: %s [OPTION] HASH FILE1 FILE2\n", Main_argv0);
	sfprintf(sfstderr, "  -1             suppres lines unique to left file\n");
	sfprintf(sfstderr, "  -2             suppres lines unique to right file\n");
	sfprintf(sfstderr, "  -3             suppres lines unique to both files\n");
	sfprintf(sfstderr, "\n");
	
	sfprintf(sfstderr, "HASH is one of the following (displayed as HASH/byte length):\n");
	Main_print_hashes();
	exit(1);	
}

int Main_process_option(int argc, char **argv, int *arg)
{
	if(strlen(argv[*arg]) != 2) Main_usage("ERROR: invalid option.");
	switch(argv[*arg][1]) {
	case '1': columns &= ~1U; return 1;
	case '2': columns &= ~2U; return 1;
	case '3': columns &= ~4U; return 1;
	}
	return 0;
}

/*
  Doesn't require files to be sorted, but must have two passes. With a command
  line option, reads all data in memory, so single pass is sufficient with
  possible risk of HUGE memory usage.
*/
void Main_process_files(const char *fname1, const char *fname2, int halg)
{
	Dt_t *set1, *set2;
	Sfio_t *file1, *file2;

	file1 = Main_file_open(fname1);
	file2 = Main_file_open(fname2);

	if((file1 == sfstdin) || (file2 == sfstdin)) {
		sfprintf(sfstderr, "FATAL: input can't be from stdin.\n");
		exit(1);
	}

	/* set CDT key size and create Vmlast heap */
	Cdt_LineDiscipline.size = hash_descriptor[halg].hashsize;
	if(!(LineSet_vmpool = vmopen(Vmdcmmap, Vmlast, VM_TRUST))) {
		sfprintf(sfstderr, "FATAL: can't create memory heap, exiting.\n");
		exit(1);
	}

	/*
	  we must read in set lines from both files before we know unique lines
	  for each file.
	*/
	if(!(set1 = LineSet_read_file(file1, halg))) {
		sfprintf(sfstderr, "ERROR calculating FILE1 hashes, exiting.\n");
		exit(1);
	}
	sfclose(file1);

	/*
	  We can't know lines unique to file1 before we read all lines from file2.
	  However, if -1 option is given we don't require output of those lines,
	  so we can output them along with reading file2.
	 */
	if(columns & 1) {
		if(!(set2 = LineSet_read_file(file2, halg))) {
			sfprintf(sfstderr, "ERROR calculating FILE2 hashes, exiting.\n");
			exit(1);
		}
		sfclose(file2);

		/* make a 2nd pass for actually outputing lines */
		file1 = Main_file_open(fname1);
		file2 = Main_file_open(fname2);
		comm(file1, set1, file2, set2, sfstdout, halg);
	} else {
		comm1(set1, file2, sfstdout, halg);
	}
}
